package com.charon.auth.filter;

import com.charon.auth.enumeration.ValidateCodeSesiionKey;
import com.charon.auth.exception.ValidateCodeException;
import com.charon.auth.handler.AuthFailureHandler;
import com.charon.auth.properties.SecurityProperties;
import com.charon.common.entity.ImageCode;
import com.charon.common.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.social.connect.web.HttpSessionSessionStrategy;
import org.springframework.social.connect.web.SessionStrategy;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

/**
 * @ClassName: ValidateCodeFilter
 * @Description: 验证码验证过滤器
 * @Author Charon [248135724@qq.com]
 * @Date 2019/1/29 22:42
 * @Version V 1.0
 */

/**
 * 关于InitializingBean：在OncePerRequestFilter已经实现了。
 *          Spring为bean提供了两种初始化bean的方式，实现InitializingBean接口，
 *      实现afterPropertiesSet方法，或者在配置文件中通过init-method指定，
 *      两种方式可以同时使用。
 *          实现InitializingBean接口是直接调用afterPropertiesSet方法，
 *      比通过反射调用init-method指定的方法效率要高一点，
 *      但是init-method方式消除了对spring的依赖。
 */
@Slf4j
public class ValidateCodeFilter extends OncePerRequestFilter {

    /**
     * 登录失败处理器
     */
    private AuthFailureHandler authFailureHandler;

    private SessionStrategy sessionStrategy=new HttpSessionSessionStrategy();

    /**
     * 配置信息：因为过滤器不是一个bean，所以此处不能使用自动注入
     */
    private SecurityProperties securityProperties;

    /**
     * 需要验证验证码的url
     */
    private Set<String> configUrls=new HashSet<>();

    @Override
    public void afterPropertiesSet() throws ServletException {
        super.afterPropertiesSet();
        String[] urls=securityProperties.getCode().getImage().getUrls().split(",");
        for(String url:urls){
            configUrls.add(url);
        }
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //此处的Url和登录认证的url是相同的，因为是在登录时验证验证码的。
        if(configUrls.contains(request.getRequestURI())&&StringUtils.equalsIgnoreCare(request.getMethod(),"post")){
            log.info("登录验证码验证！");
            //当验证码认证失败时会抛出自定义的异常，捕获到该异常使用登录失败处理器处理。
            try {
                validate(new ServletWebRequest(request));
            }catch (ValidateCodeException e){
                e.printStackTrace();
                authFailureHandler.onAuthenticationFailure(request,response,e);
                return;
            }
        }
        filterChain.doFilter(request,response);
    }

    private void validate(ServletWebRequest servletWebRequest) throws ValidateCodeException, ServletRequestBindingException {
        //取出后台生成验证码时放进去的验证码对象
        ImageCode codeInSession=(ImageCode)sessionStrategy.getAttribute(servletWebRequest, ValidateCodeSesiionKey.SESSION_KEY.getTitle());
        //取出前台登录时用户输入的验证码信息
        String codeInRequest= ServletRequestUtils.getStringParameter(servletWebRequest.getRequest(),"imageCode");
        if(!StringUtils.isEmpty(codeInRequest)){
            throw new ValidateCodeException("验证码的值不能为空");
        }
        if(codeInSession==null){
            throw new ValidateCodeException("验证码不存在");
        }
        if(codeInSession.isExpried()){
            //从session中移除该属性
            sessionStrategy.removeAttribute(servletWebRequest, ValidateCodeSesiionKey.SESSION_KEY.getTitle());
            throw new ValidateCodeException("验证码已过期");
        }
        System.out.println("请求验证码："+codeInSession.getCode()+"   Request验证码："+codeInRequest);
        if(!StringUtils.equalsIgnoreCare(codeInSession.getCode(),codeInRequest)){
            throw new ValidateCodeException("验证码不匹配");
        }
        //如果能走到这，说明验证成功了。
        sessionStrategy.removeAttribute(servletWebRequest,ValidateCodeSesiionKey.SESSION_KEY.getTitle());
    }

    public void setAuthFailureHandler(AuthFailureHandler authFailureHandler) {
        this.authFailureHandler = authFailureHandler;
    }
}
